// This is unpublished source code. Michah Lerner 2006

package trieMatch.keywordMatcher;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;

import trieMatch.Constants;
import trieMatch.Interfaces.Similarity;

/**
 * Heuristic scoring by word and string comparisons, considering word order and size. Hand tuned.
 * 
 * <br>
 * <strong>NOTE: for good performance</strong> the trie storage should support fast prefix lookup, for example with
 * {@link java.util.SortedSet}. The methods of <b>enum</b>
 * {@link trieMatch.KeywordMatcher.KeyMatch.MatcherActionDefinition} specify the correct pieces (see
 * {@link trieMatch.KeywordMatcher.KeyMatch}) <br>
 * <br>
 * 
 * These are also selected automatically when running <code>MultiQuery</code> with the option <em>-exp</em>, as
 * this uses the <code>public class SortedtrieMatchEXP extends TrieMatcher</code> 
 * <code>MultiQuery</code>.
 * 
 * @author Michah.Lerner
 * 
 */

public class ResultScorer_prefixPhrase implements Similarity {

	/**
	 * Distinguish scoring with partial matching to prefixes. An exact prefix is the better match.
	 * 
	 * @param keyString
	 *            first string
	 * @param otherString
	 *            second string
	 * @return subjective quality score
	 */
	public static float prefixMatch(final String keyString, final String otherString) {
		if (keyString == null || otherString == null || keyString.length() == 0 || !otherString.startsWith(keyString)) {
			return 0F;
		}
		for (int prefix = 0; prefix < keyString.length(); prefix++) {
			if (keyString.charAt(prefix) != otherString.charAt(prefix)) {
				return (float) (prefix * prefix) / keyString.length() / otherString.length();
			}
		}
		return 1F;
	}

	/**
	 * Subjective quality score between two structures
	 * 
	 * @param other
	 *            Structure to compare with <code>this</code>
	 * @return subjective quality score
	 */
	public float sim(final StructuredElement element, final StructuredElement other) {
		final ArrayList<String> a1 = new ArrayList<String>(element.size());
		final ArrayList<String> a2 = new ArrayList<String>(element.size());
		for (final Atom a : element) {
			a1.add(a.getValue());
		}
		for (final Atom a : other) {
			a2.add(a.getValue());
		}
		final float res = sim(a1, a2);
		return res;
	}

	public float sim(final StructuredElement element, final String other) {
		final List<String> a1 = new ArrayList<String>(element.size());
		for (final Atom a : element) {
			a1.add(a.getValue());
		}
		final float res = sim(a1, Arrays.asList(other.toLowerCase().split("[^A-Za-z0-9]")));
		return res;
	}

	public float sim(final List<String> l1, final List<String> l2) {
		double simc = 0D;
		int pos = 0;
		int numMatched = 0;
		final TreeSet<String> tset = new TreeSet<String>(l2);
		final Iterator<String> i1 = l1.iterator();
		final Iterator<String> i2 = l2.iterator();
		while (i1.hasNext() && i2.hasNext()) {
			final String s1 = i1.next();
			final String s2 = i2.next();
			pos++;
			float plen = prefixMatch(s1, s2); // score the words' inorder fit
			if (plen > 0) {
				plen += Math.pow(Constants.INORDERbonus, pos); // boost towards head
			}
			final float olen = prefixMatch(s1, leastKnownElement(s1, tset)); // unboosted out-of-order fit
			final float mval = Math.max(plen, olen);
			if (mval > 0) {
				numMatched++;
				simc += mval;
			}
		}
		final int maxlen = Math.max(l1.size(), l2.size());
		final float matchedTokenRatio = (float) numMatched / maxlen; // length-adjust
		final float result = (float) (simc * simc * matchedTokenRatio);
		return result;
	}

	/**
	 * Lookahead function, to the successor element that is not a prefix.
	 * 
	 * @param string
	 *            item to look for
	 * @param set
	 *            items to look in
	 * @return subset to look in
	 */

	String leastKnownElement(final String string, final SortedSet<String> set) {
		final SortedSet<String> tail = set.tailSet(string);
		if (tail.size() == 0) {
			return null;
		}
		return tail.first();
	}
}
